home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
The Fatted Calf
/
The Fatted Calf.iso
/
Modules
/
BackSpaceModules
/
Source
/
Polyhedra
/
PolyhedraView.m
< prev
next >
Wrap
Text File
|
1992-04-16
|
28KB
|
934 lines
//
// RegularPolyhedraView - a flexible, bouncing polyhedron.
//
// Module for BackSpace.app
// 22 Nov 91 - 8 Dec 91.
// Simon Marchant, and Paul Brown (simon@math.berkeley.edu,
// pbrown@math.berkeley.edu).
//
#import "PolyhedraView.h"
#import "PolyhedraViewWraps.h"
#import <appkit/graphics.h>
#import <appkit/Matrix.h>
#import <libc.h>
#import <dpsclient/wraps.h>
#import <math.h>
#import <appkit/publicWraps.h>
// Number of vertices of the polyhedron
static int theNumVertices[NUM_POLYHEDRA] =
{4, 8, 6, 20, 12};
// Number of vertices adjacent to a vertex.
static int theNumAdjacents[NUM_POLYHEDRA] =
{3, 3, 4, 3, 5};
// Number of faces of the polyhedron.
static int theNumFaces[NUM_POLYHEDRA] =
{4, 6, 8, 12, 20};
// Number of vertices on each face.
static int theVerticesPerFace[NUM_POLYHEDRA] =
{3, 4, 3, 5, 3};
// Number of non NO_DRAW faces - i.e. faces that we actually bother drawing.
static int realFaces[NUM_POLYHEDRA] =
{3, 4, 4, 9, 14};
// Numbers describing the 3D co-ordinates of the polyhedra - the initial positions.
static D3_PT offsets[NUM_POLYHEDRA][MAX_NUM_VERTICES] = {
{{ 0, 0, 1.73205}, // Tetrahedron
{ 0, 1.63299, -0.57735},
{-1.41421, -0.816497, -0.57735},
{ 1.41421, -0.816497, -0.57735},
{ 0, 0, 0},
{ 0, 0, 0},
{ 0, 0, 0},
{ 0, 0, 0},
{ 0, 0, 0},
{ 0, 0, 0},
{ 0, 0, 0},
{ 0, 0, 0},
{ 0, 0, 0},
{ 0, 0, 0},
{ 0, 0, 0},
{ 0, 0, 0},
{ 0, 0, 0},
{ 0, 0, 0},
{ 0, 0, 0},
{ 0, 0, 0}},
{{ 0.707107, 0.707107, 0.707107}, // Cube.
{-0.707107, 0.707107, 0.707107},
{-0.707107, -0.707107, 0.707107},
{ 0.707107, -0.707107, 0.707107},
{-0.707107, -0.707107, -0.707107},
{ 0.707107, -0.707107, -0.707107},
{ 0.707107, 0.707107, -0.707107},
{-0.707107, 0.707107, -0.707107},
{ 0, 0, 0},
{ 0, 0, 0},
{ 0, 0, 0},
{ 0, 0, 0},
{ 0, 0, 0},
{ 0, 0, 0},
{ 0, 0, 0},
{ 0, 0, 0},
{ 0, 0, 0},
{ 0, 0, 0},
{ 0, 0, 0},
{ 0, 0, 0}},
{{ 0, 0, 1.41421}, // Octahedron
{ 1.41421, 0, 0},
{ 0, 1.41421, 0},
{ 0, 0, -1.41421},
{-1.41421, 0, 0},
{ 0, -1.41421, 0},
{ 0, 0, 0},
{ 0, 0, 0},
{ 0, 0, 0},
{ 0, 0, 0},
{ 0, 0, 0},
{ 0, 0, 0},
{ 0, 0, 0},
{ 0, 0, 0},
{ 0, 0, 0},
{ 0, 0, 0},
{ 0, 0, 0},
{ 0, 0, 0},
{ 0, 0, 0},
{ 0, 0, 0}},
{{ 0.525731, 0.381966, 0.850651}, //Dodecahedron.
{-0.200811, 0.618034, 0.850651},
{-0.649839, 0, 0.850651},
{-0.200811, -0.618034, 0.850651},
{ 0.525731, -0.381966, 0.850651},
{ 0.850651, 0.618034, 0.200811},
{-0.32492, 1.0, 0.200811},
{-1.05146, 0, 0.200811},
{-0.32492, -1.0, 0.200811},
{ 0.850651, -0.618034, 0.200811},
{ 0.32492, 1.0, -0.200811},
{-0.850651, 0.618034, -0.200811},
{-0.850651, -0.618034, -0.200811},
{ 0.32492, -1.0, -0.200811},
{ 1.05146, 0, -0.200811},
{ 0.200811, 0.618034, -0.850651},
{-0.525731, 0.381966, -0.850651},
{-0.525731, -0.381966, -0.850651},
{ 0.200811, -0.618034, -0.850651},
{ 0.649839, 0, -0.850651}},
{{ 0, 0, 1.0}, // Icosahedron.
{ 0.894427, 0, 0.447214},
{ 0.276393, 0.850651, 0.447214},
{-0.723607, 0.525731, 0.447214},
{-0.723607, -0.525731, 0.447214},
{ 0.276393, -0.850651, 0.447214},
{ 0.723607, 0.525731, -0.447214},
{-0.276393, 0.850651, -0.447214},
{-0.894427, 0, -0.447214},
{-0.276393, -0.850651, -0.447214},
{ 0.723607, -0.525731, -0.447214},
{ 0, 0, -1.0},
{ 0, 0, 0},
{ 0, 0, 0},
{ 0, 0, 0},
{ 0, 0, 0},
{ 0, 0, 0},
{ 0, 0, 0},
{ 0, 0, 0},
{ 0, 0, 0}}};
// List of faces in the polyhedron.
static int faces[NUM_POLYHEDRA][MAX_NUM_FACES][MAX_VERTICES_PER_FACE] = {
{{0, 1, 2, -1, -1}, // Tetrahedron.
{0, 2, 3, -1, -1},
{0, 3, 1, -1, -1},
{1, 3, 2, -1, -1},
{-1, -1, -1, -1, -1},
{-1, -1, -1, -1, -1},
{-1, -1, -1, -1, -1},
{-1, -1, -1, -1, -1},
{-1, -1, -1, -1, -1},
{-1, -1, -1, -1, -1},
{-1, -1, -1, -1, -1},
{-1, -1, -1, -1, -1},
{-1, -1, -1, -1, -1},
{-1, -1, -1, -1, -1},
{-1, -1, -1, -1, -1},
{-1, -1, -1, -1, -1},
{-1, -1, -1, -1, -1},
{-1, -1, -1, -1, -1},
{-1, -1, -1, -1, -1},
{-1, -1, -1, -1, -1}},
{{0, 1, 2, 3, -1}, // Cube.
{0, 3, 5, 6, -1},
{0, 6, 7, 1, -1},
{1, 7, 4, 2, -1},
{4, 7, 6, 5, -1},
{2, 4, 5, 3, -1},
{-1, -1, -1, -1, -1},
{-1, -1, -1, -1, -1},
{-1, -1, -1, -1, -1},
{-1, -1, -1, -1, -1},
{-1, -1, -1, -1, -1},
{-1, -1, -1, -1, -1},
{-1, -1, -1, -1, -1},
{-1, -1, -1, -1, -1},
{-1, -1, -1, -1, -1},
{-1, -1, -1, -1, -1},
{-1, -1, -1, -1, -1},
{-1, -1, -1, -1, -1},
{-1, -1, -1, -1, -1},
{-1, -1, -1, -1, -1}},
{{0, 1, 2, -1, -1}, // Octahedron.
{0, 2, 4, -1, -1},
{0, 4, 5, -1, -1},
{0, 5, 1, -1, -1},
{1, 5, 3, -1, -1},
{1, 3, 2, -1, -1},
{3, 5, 4, -1, -1},
{2, 3, 4, -1, -1},
{-1, -1, -1, -1, -1},
{-1, -1, -1, -1, -1},
{-1, -1, -1, -1, -1},
{-1, -1, -1, -1, -1},
{-1, -1, -1, -1, -1},
{-1, -1, -1, -1, -1},
{-1, -1, -1, -1, -1},
{-1, -1, -1, -1, -1},
{-1, -1, -1, -1, -1},
{-1, -1, -1, -1, -1},
{-1, -1, -1, -1, -1},
{-1, -1, -1, -1, -1}},
{{0, 1, 2, 3, 4}, // Dodecahedron.
{0, 4, 9, 14, 5},
{0, 5, 10, 6, 1},
{1, 6, 11, 7, 2},
{2, 7, 12, 8, 3},
{3, 4, 9, 13, 8},
{5, 14, 19, 15, 10},
{6, 10, 15, 16, 11},
{7, 11, 16, 17, 12},
{8, 12, 17, 18, 13},
{9, 13, 18, 19, 14},
{15, 19, 18, 17, 16},
{-1, -1, -1, -1, -1},
{-1, -1, -1, -1, -1},
{-1, -1, -1, -1, -1},
{-1, -1, -1, -1, -1},
{-1, -1, -1, -1, -1},
{-1, -1, -1, -1, -1},
{-1, -1, -1, -1, -1},
{-1, -1, -1, -1, -1}},
{{0, 2, 1, -1, -1}, // Icosahedron.
{0, 3, 2, -1, -1},
{0, 4, 3, -1, -1},
{0, 5, 4, -1, -1},
{0, 1, 5, -1, -1},
{1, 2, 6, -1, -1},
{2, 3, 7, -1, -1},
{3, 4, 8, -1, -1},
{4, 5, 9, -1, -1},
{5, 1, 10, -1, -1},
{6, 2, 7, -1, -1},
{7, 3, 8, -1, -1},
{8, 4, 9, -1, -1},
{9, 5, 10, -1, -1},
{10, 1, 6, -1, -1},
{6, 7, 11, -1, -1},
{7, 8, 11, -1, -1},
{8, 9, 11, -1, -1},
{9, 10, 11, -1, -1},
{10, 6, 11, -1, -1}}};
// Array following contains the vertex adjacency information, so we can
// determine which n vertices are adjacent to any given one.
typedef struct { float r,g,b; } rgbcolor;
static rgbcolor clut[5] = {
{1,0,0},
{0,1,.2},
{0,0,1},
{1,.8,0},
{1,.3,0}
};
// Two "pseudo-colours".
#define TRANSPARENT -1
#define NO_DRAW -2
// Colour that you draw the face with.
// TRANSPARENT means just draw the edges.
// NO_DRAW means that the face is transparent, and other faces
// draw all the edges of the face - so no need to draw it.
static int faceColour[NUM_POLYHEDRA][MAX_NUM_FACES] =
{{0, TRANSPARENT, TRANSPARENT, NO_DRAW,
TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT,
TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT,
TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT,
TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT},
{1, NO_DRAW, TRANSPARENT, NO_DRAW,
2, TRANSPARENT, TRANSPARENT, TRANSPARENT,
TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT,
TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT,
TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT},
{3, NO_DRAW, 4, NO_DRAW,
0, NO_DRAW, NO_DRAW, 1,
TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT,
TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT,
TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT},
{2, NO_DRAW, TRANSPARENT, NO_DRAW,
TRANSPARENT, TRANSPARENT, TRANSPARENT, 3,
TRANSPARENT, NO_DRAW, 4, TRANSPARENT,
TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT,
TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT},
{0, TRANSPARENT, 1, NO_DRAW, TRANSPARENT,
NO_DRAW, NO_DRAW, NO_DRAW, 2, 3,
4, 0, TRANSPARENT, TRANSPARENT, TRANSPARENT,
TRANSPARENT, NO_DRAW, 1, NO_DRAW, 2}};
@implementation PolyhedraView
// Distance between two points. Inlined for efficiency.
inline float distance(NXCoord xcrd, NXCoord ycrd, NXCoord zcrd);
inline float distance(NXCoord xcrd, NXCoord ycrd, NXCoord zcrd)
{
return sqrt(xcrd * xcrd + ycrd * ycrd + zcrd * zcrd);
}
// Draw a line in the proper perspectice projection from pt1 to pt2
- perspectiveLineFrom:(D3_PT)pt1 to:(D3_PT)pt2
{
if (perspectivePt.z == 0)
return self;
PSmoveto(pt1.x - pt1.z * (pt1.x - perspectivePt.x) / perspectivePt.z,
pt1.y - pt1.z * (pt1.y - perspectivePt.y) / perspectivePt.z);
PSlineto(pt2.x - pt2.z * (pt2.x - perspectivePt.x) / perspectivePt.z,
pt2.y - pt2.z * (pt2.y - perspectivePt.y) / perspectivePt.z);
return self;
}
// Draw the background room - in the appropriate gray.
- drawBoxInColour:(float)theGray
{
return self; //by sam, I don't like the room!
#ifdef VANNA
D3_PT pt1, pt2;
PSsetgray (theGray);
pt1.x = 0;
pt1.y = 0;
pt1.z = 0;
pt2.x = 0;
pt2.y = 0;
pt2.z = backTopRight.z;
[self perspectiveLineFrom:pt1 to:pt2];
pt1.z = backTopRight.z;
pt1.y = backTopRight.y;
[self perspectiveLineFrom:pt2 to:pt1];
pt2.z = 0;
pt2.y = backTopRight.y;
[self perspectiveLineFrom:pt1 to:pt2];
pt2 = backTopRight;
[self perspectiveLineFrom:pt1 to:pt2];
pt1.x = backTopRight.x;
pt1.z = 0;
[self perspectiveLineFrom:pt2 to:pt1];
pt1.y = 0;
pt1.z = backTopRight.z;
[self perspectiveLineFrom:pt2 to:pt1];
pt2.y = 0;
pt2.z = 0;
[self perspectiveLineFrom:pt1 to:pt2];
pt2.z = backTopRight.z;
pt2.x = 0;
[self perspectiveLineFrom:pt1 to:pt2];
PSstroke();
return self;
#endif
}
// Draw the polyhedron at the current position.
- drawPolyhedron
{
int i, j, k, m, n=0;
float faceVerticesZ[MAX_NUM_FACES][MAX_VERTICES_PER_FACE];
float sortedVerticesZ[MAX_NUM_FACES][MAX_VERTICES_PER_FACE];
NXPoint faceVerticesScreen[MAX_NUM_FACES][MAX_VERTICES_PER_FACE];
BOOL drawn[MAX_NUM_FACES];
BOOL intersect;
NXPoint *thisFace, *tempFace, *firstVertex, *secondVertex, *thirdVertex, *fourthVertex;
int colours[MAX_NUM_FACES];
float r[MAX_NUM_FACES];
float g[MAX_NUM_FACES];
float b[MAX_NUM_FACES];
VERTEX *thisVertex;
float firstVertexZ, secondVertexZ, thirdVertexZ, fourthVertexZ;
float det, s=0, t=0;
// Pre-compute the positions of each of the vertices as they're drawn on the screen. We'll need
// them a little later, and we'll keep them around until we erase this polyhedron from the screen.
for (i = 0; i < numVertices; i++)
{
thisVertex = vertices + i;
thisVertex->screenPos.x = perspectivePt.x + (thisVertex->pos.x - perspectivePt.x) * perspectivePt.z / (perspectivePt.z + thisVertex->pos.z);
thisVertex->screenPos.y = perspectivePt.y + (thisVertex->pos.y - perspectivePt.y) * perspectivePt.z / (perspectivePt.z + thisVertex->pos.z);
}
// Pick out the faces we have to draw, and grab an ordered list of the z-coordinates of each
// vertex in each face, for later on.
k = 0;
for (i = 0; i < numFaces; i++)
if (faceColour[polyhedron][i] != NO_DRAW)
{
for (j = 0; j < verticesPerFace; j++)
{
thisVertex = &( vertices[faces[polyhedron][i][j]]);
faceVerticesScreen[k][j] = thisVertex -> screenPos;
faceVerticesZ[k][j] = thisVertex->pos.z;
for (m = 0; (m < j) && (sortedVerticesZ[k][m] > thisVertex->pos.z); m++)
;
for (n = j; n > m; n--)
sortedVerticesZ[k][n] = sortedVerticesZ[k][n - 1];
sortedVerticesZ[k][m] = thisVertex->pos.z;
}
colours[k] = faceColour[polyhedron][i];
if (colours[k] != TRANSPARENT)
{
r[k] = clut[colours[k]].r;
g[k] = clut[colours[k]].g;
b[k] = clut[colours[k]].b;
}
drawn[k] = NO;
k++;
}
// Now, run through the list of faces we have to draw, and select the next one to
// draw - by making sure that it's not in front of any faces we haven't drawn
// yet.
for (i = 0; i < numDrawFaces; i++)
{
for (k = 0; drawn[k]; k++)
;
thisFace = (NXPoint *)&(faceVerticesScreen[k]);
for (j = k + 1; j < numDrawFaces; j++)
if (!drawn[j])
{
tempFace = (NXPoint *)&(faceVerticesScreen[j]);
intersect = NO;
// check for edges intersecting.
for (m = 0; (!intersect) && (m < verticesPerFace); m++)
for (n = 0; (!intersect) && (n < verticesPerFace); n++)
{
firstVertex = thisFace + m;
secondVertex = (m + 1 == verticesPerFace) ? thisFace : thisFace + m + 1;
thirdVertex = tempFace + n;
fourthVertex = (n + 1 == verticesPerFace) ? tempFace : tempFace + n + 1;
if (((firstVertex->x != thirdVertex->x) || (firstVertex->y != thirdVertex->y)) &&
((firstVertex->x != fourthVertex->x) || (firstVertex->y != fourthVertex->y)) &&
((secondVertex->x != thirdVertex->x) || (secondVertex->y != thirdVertex->y)) &&
((secondVertex->x != fourthVertex->x) || (secondVertex->y != fourthVertex->y)))
if ((det = ((firstVertex->x - secondVertex->x) * (fourthVertex->y - thirdVertex->y) -
(firstVertex->y - secondVertex->y) * (fourthVertex->x - thirdVertex->x))) != 0)
{
t = ((fourthVertex->y - thirdVertex->y) * (fourthVertex->x - secondVertex->x) +
(thirdVertex->x - fourthVertex->x) * (fourthVertex->y - secondVertex->y)) / det;
s = ((secondVertex->y - firstVertex->y) * (fourthVertex->x - secondVertex->x) +
(firstVertex->x - secondVertex->x) * (fourthVertex->y - secondVertex->y)) / det;
if ((t > 0.0) && (t < 1.0) && (s > 0.0) & (s < 1.0))
intersect = YES;
}
}
m --;
n --;
// if no edges intersect, order by z-coordinates.
if (!intersect)
{
for (m = 0; (m < verticesPerFace) && (sortedVerticesZ[k][m] == sortedVerticesZ[j][m]); m++)
;
if ((m != verticesPerFace) && (sortedVerticesZ[j][m] > sortedVerticesZ[k][m]))
{
k = j;
thisFace = tempFace;
}
// else
// ;
}
else
// if there's a pair of edges intersecting, look at the z-coord at the
// intersection pt - the largest z-coord is the one we drawn.
{
firstVertexZ = faceVerticesZ[k][m];
secondVertexZ = (m + 1 == verticesPerFace) ? faceVerticesZ[k][0] :
faceVerticesZ[k][m + 1];
thirdVertexZ = faceVerticesZ[j][n];
fourthVertexZ = (n + 1 == verticesPerFace) ? faceVerticesZ[j][0] :
faceVerticesZ[j][n + 1];
if (firstVertexZ * t + secondVertexZ * (1 - t) < thirdVertexZ * s + fourthVertexZ * (1 - s))
{
k = j;
thisFace = tempFace;
}
}
}
// Let the wraps do the drawing, depending on the number of vertices in
// the face, and whether or not we should fill the face.
if (verticesPerFace == 3)
if (colours[k] != TRANSPARENT)
colourTriangle(thisFace[0].x, thisFace[0].y, thisFace[1].x, thisFace[1].y,
thisFace[2].x, thisFace[2].y, r[k],g[k],b[k]);
else
outlineTriangle(thisFace[0].x, thisFace[0].y, thisFace[1].x, thisFace[1].y,
thisFace[2].x, thisFace[2].y);
else
if (verticesPerFace == 4)
if (colours[k] != TRANSPARENT)
colourSquare(thisFace[0].x, thisFace[0].y, thisFace[1].x, thisFace[1].y,
thisFace[2].x, thisFace[2].y, thisFace[3].x, thisFace[3].y,
r[k],g[k],b[k]);
else
outlineSquare(thisFace[0].x, thisFace[0].y, thisFace[1].x, thisFace[1].y,
thisFace[2].x, thisFace[2].y, thisFace[3].x, thisFace[3].y);
else
if (verticesPerFace == 5)
if (colours[k] != TRANSPARENT)
colourPentagon(thisFace[0].x, thisFace[0].y, thisFace[1].x, thisFace[1].y,
thisFace[2].x, thisFace[2].y, thisFace[3].x, thisFace[3].y,
thisFace[4].x, thisFace[4].y, r[k],g[k],b[k]);
else
outlinePentagon(thisFace[0].x, thisFace[0].y, thisFace[1].x, thisFace[1].y,
thisFace[2].x, thisFace[2].y, thisFace[3].x, thisFace[3].y,
thisFace[4].x, thisFace[4].y);
drawn[k] = YES;
}
return self;
}
// Erase the polyhedron. Quick, and dirty - just erase a large rectangle that
// covers the entire polyhedron on the screen - if it erase some of the background, so
// what? We'll redraw that in a little while, anyway. This method has the prime virtue of
// being fast, fast, fast.
- erasePolyhedron
{
int i;
NXRect eraseRect;
float maxX, maxY, minX, minY;
NXPoint thisPt;
maxY = maxX = 0;
minX = frame.size.width;
minY = frame.size.height;
for (i = 0; i < numVertices; i++)
{
thisPt = vertices[i].screenPos;
if (thisPt.x > maxX)
maxX = thisPt.x;
if (thisPt.y > maxY)
maxY = thisPt.y;
if (thisPt.x < minX)
minX = thisPt.x;
if (thisPt.y < minY)
minY = thisPt.y;
}
eraseRect.origin.x = minX;
eraseRect.origin.y = minY;
eraseRect.size.width = maxX - minX + 1;
eraseRect.size.height = maxY - minY + 1;
PSsetgray(NX_BLACK);
NXRectFill(&eraseRect);
return self;
}
// Do one animation step.
- oneStep
{
int i, j;
float length;
float dotProduct;
D3_PT velForce[MAX_NUM_VERTICES], force[MAX_NUM_VERTICES];
float theForce;
D3_PT sumVel;
// Cycle the background box colours.
if (((backStep ++) % 20) == 0)
{
switch (backStep / 20)
{
#ifdef VANNA
case 0:
[self drawBoxInColour:NX_WHITE];
break;
case 1:
[self drawBoxInColour:NX_LTGRAY];
break;
case 2:
[self drawBoxInColour:NX_DKGRAY];
break;
case 3:
[self drawBoxInColour:NX_BLACK];
break;
#endif
default:
// Compute average velocity of icosahedron
// If it's too small, give it a random kick
sumVel.x = sumVel.y = sumVel.z = 0;
for (i = 0; i < numVertices; i++)
{
sumVel.x += vertices[i].vel.x;
sumVel.y += vertices[i].vel.y;
sumVel.z += vertices[i].vel.z;
}
if (distance(sumVel.x, sumVel.y, sumVel.z) / numVertices < 0.5) //.33
{
sumVel.x = randBetween(-INIT_VELOCITY, INIT_VELOCITY);
sumVel.y = randBetween(-INIT_VELOCITY, INIT_VELOCITY);
sumVel.z = randBetween(-INIT_VELOCITY, INIT_VELOCITY);
for (i = 0; i < numVertices; i++)
{
vertices[i].vel.x += sumVel.x;
vertices[i].vel.y += sumVel.y;
vertices[i].vel.z += sumVel.z;
}
}
backStep = 0;
break;
}
}
// If we're not doing anything about the polyhedron, leave now.
if (noAnimation)
return self;
// Erase it.
[self erasePolyhedron];
// Move it, bouncing off walls as necessary
for (i = 0; i < numVertices; i++)
{
vertices[i].pos.x += vertices[i].vel.x;
if ((vertices[i].pos.x < 0) || (vertices[i].pos.x > backTopRight.x))
vertices[i].vel.x = -vertices[i].vel.x;
vertices[i].pos.y += vertices[i].vel.y;
if ((vertices[i].pos.y < 0) || (vertices[i].pos.y > backTopRight.y))
vertices[i].vel.y = -vertices[i].vel.y;
vertices[i].pos.z += vertices[i].vel.z;
if ((vertices[i].pos.z < 0) || (vertices[i].pos.z > backTopRight.z))
vertices[i].vel.z = -vertices[i].vel.z;
}
// draw it
[self drawPolyhedron];
for (i = 0; i < numVertices; i++)
{
velForce[i].x = force[i].x = 0;
velForce[i].y = force[i].y = 0;
velForce[i].z = force[i].z = 0;
}
// calculate the force on each vertex.
// Notice the use of symmetry here to cut down the amount of computation
// i.e. the force on vertex j exerted by the spring from vertex i is minus
// that on vertex i exerted by vertex j .....
for (i = 0; i < numVertices; i++)
{
for (j = i + 1; j < numVertices; j++)
{
// spring forces (Hookes' Law - remember that?)
length = distance(vertices[i].pos.x - vertices[j].pos.x,
vertices[i].pos.y - vertices[j].pos.y,
vertices[i].pos.z - vertices[j].pos.z);
theForce = (vertices[i].pos.x - vertices[j].pos.x) / length * (restLengths[i][j] - length) * SPRING_K;
force[i].x += theForce;
force[j].x -= theForce;
theForce = (vertices[i].pos.y - vertices[j].pos.y) / length * (restLengths[i][j] - length) * SPRING_K;
force[i].y += theForce;
force[j].y -= theForce;
theForce = (vertices[i].pos.z - vertices[j].pos.z) / length * (restLengths[i][j] - length) * SPRING_K;
force[i].z += theForce;
force[j].z -= theForce;
// Velocity damping - only for adjacent vertices
if (isAdjacent[i][j])
{
dotProduct = ((vertices[i].pos.x - vertices[j].pos.x) * (vertices[i].vel.x - vertices[j].vel.x) +
(vertices[i].pos.y - vertices[j].pos.y) * (vertices[i].vel.y - vertices[j].vel.y) +
(vertices[i].pos.z - vertices[j].pos.z) * (vertices[i].vel.z - vertices[j].vel.z)) / length/length * damping;
theForce = dotProduct * (vertices[i].pos.x - vertices[j].pos.x);
velForce[i].x -= theForce;
velForce[j].x += theForce;
theForce = dotProduct * (vertices[i].pos.y - vertices[j].pos.y);
velForce[i].y -= theForce;
velForce[j].y += theForce;
theForce = dotProduct * (vertices[i].pos.z - vertices[j].pos.z); //xxx
velForce[i].z -= theForce;
velForce[j].z += theForce;
}
}
}
// Change the velocities (F = ma !!). Make sure the velocities don't get too big.
// (Stability check).
for (i = 0; i < numVertices; i++)
{
vertices[i].vel.x += (velForce[i].x + force[i].x) / vertices[i].mass;
if (fabs(vertices[i].vel.x) > MAX_VEL)
vertices[i].vel.x = vertices[i].vel.x / fabs(vertices[i].vel.x) * MAX_VEL;
vertices[i].vel.y += (velForce[i].y + force[i].y) / vertices[i].mass;
if (fabs(vertices[i].vel.y) > MAX_VEL)
vertices[i].vel.y = vertices[i].vel.y / fabs(vertices[i].vel.y) * MAX_VEL;
vertices[i].vel.z += (velForce[i].z + force[i].z) / vertices[i].mass;
if (fabs(vertices[i].vel.z) > MAX_VEL)
vertices[i].vel.z = vertices[i].vel.z / fabs(vertices[i].vel.z) * MAX_VEL;
}
return self;
}
// Just erase ourself.
- drawSelf:(const NXRect *)rects :(int)rectCount
{
if (!rects || !rectCount)
return self;
PSsetlinewidth(0);
PSsetgray(0);
NXRectFill(rects);
return self;
}
// Somebody just changed the size of the box we're sitting in - recompute
// stuff, and start the animation again.
- frameChanged:(const NXRect *)frameRect
{
D3_PT initPos, initVel;
int i, j;
float length;
[self useNewFrame:frameRect];
// Compute the room size.
backTopRight.x = frameRect->size.width;
backTopRight.y = frameRect->size.height;
backTopRight.z = frameRect->size.height;
// Where the perspective's coming from.
perspectivePt.x = backTopRight.x / 2;
perspectivePt.y = backTopRight.y / 2;
perspectivePt.z = backTopRight.y * DEPTH;
// Compute initial velocity.
initVel.x = randBetween(-INIT_VELOCITY, INIT_VELOCITY);
initVel.y = randBetween(-INIT_VELOCITY, INIT_VELOCITY);
initVel.z = randBetween(-INIT_VELOCITY, INIT_VELOCITY);
// If the room's too small, we're going to have problems, so don't
// stick the polyhedron in.
if ((frameRect->size.width < 240) || (frameRect->size.height < 240))
noAnimation = YES;
else
{
noAnimation = NO;
// Compute initial position.
initPos.x = randBetween(120, frameRect->size.width - 120);
initPos.y = randBetween(120, frameRect->size.height - 120);
initPos.z = randBetween(120, frameRect->size.height - 120);
// Compute the rest lengths of the springs.
length = distance(offsets[polyhedron][0].x - offsets[polyhedron][1].x, offsets[polyhedron][0].y - offsets[polyhedron][1].y, offsets[polyhedron][0].z - offsets[polyhedron][1].z);
for (i = 0; i < numVertices; i++)
{
vertices[i].pos.x = initPos.x + offsets[polyhedron][i].x * SPRING_REST_LEN / length;
vertices[i].pos.y = initPos.y + offsets[polyhedron][i].y * SPRING_REST_LEN / length;
vertices[i].pos.z = initPos.z + offsets[polyhedron][i].z * SPRING_REST_LEN / length;
vertices[i].vel = initVel;
}
for (i = 0; i < numVertices; i++)
for (j = 0; j < numVertices; j++)
{
length = distance(vertices[i].pos.x - vertices[j].pos.x, vertices[i].pos.y - vertices[j].pos.y,
vertices[i].pos.z - vertices[j].pos.z);
restLengths[i][j] = length;
}
}
// Compute the damping factor
damping = DAMPING * realAdjacents / numVertices;
// sanity check: if this number is too big, then velocity
// damping contributes to instability, rather than curing it...
if (damping > 0.3)
damping = 0.3;
return self;
}
// If we get either setFrame, or sizeTo messages, we'd better recompute the
// box and stuff.
- setFrame:(const NXRect *)frameRect
{
[super setFrame:frameRect];
[self frameChanged: frameRect];
return self;
}
- sizeTo:(NXCoord)width :(NXCoord)height
{
NXRect frameRect;
[super sizeTo:width:height];
frameRect.origin.x = 0;
frameRect.origin.y = 0;
frameRect.size.width = width;
frameRect.size.height = height;
[self frameChanged: &frameRect];
return self;
}
- initFrame:(const NXRect *)frameRect
{
[super initFrame: frameRect];
[self useNewFrame:frameRect];
if (frameRect != NULL)
[self setFrame:frameRect];
return self;
}
- useNewFrame:(const NXRect *)frameRect
{
int i, j, k;
D3_PT initVel;
BOOL foundVertex;
// Decide which Polyhedron.
if (selectedIndex == 0) polyhedron = random() % 5;
else polyhedron = selectedIndex-1;
numVertices = theNumVertices[polyhedron];
numAdjacents = theNumAdjacents[polyhedron];
numFaces = theNumFaces[polyhedron];
numDrawFaces = realFaces[polyhedron];
verticesPerFace = theVerticesPerFace[polyhedron];
// Compute adjacency info.
// Notice that for the purposes of velocity damping, adjacent
// is the same as "are vertices on the same face." Not "are
// vertices on the same edge."
for (i = 0; i < numVertices; i++)
{
for (j = 0; j < numVertices; j++)
isAdjacent[i][j] = NO;
for (j = 0; j < numFaces; j++)
{
foundVertex = NO;
for (k = 0; k < verticesPerFace; k++)
if (faces[polyhedron][j][k] == i)
foundVertex = YES;
if (foundVertex)
for (k = 0; k < verticesPerFace; k++)
if (faces[polyhedron][j][k] != i)
isAdjacent[i][faces[polyhedron][j][k]] = YES;
}
}
realAdjacents = 0;
for (i = 0; i < numVertices; i++)
if (isAdjacent[0][i])
realAdjacents ++;
backTopRight.x = 0;
backTopRight.y = 0;
backTopRight.z = 0;
perspectivePt.x = 0;
perspectivePt.y = 0;
perspectivePt.z = 0;
noAnimation = YES;
initVel.x = randBetween(-INIT_VELOCITY, INIT_VELOCITY);
initVel.y = randBetween(-INIT_VELOCITY, INIT_VELOCITY);
initVel.z = randBetween(-INIT_VELOCITY, INIT_VELOCITY);
// set up the initial position of the icosahedron.
for (i = 0; i < numVertices; i++)
{
vertices[i].mass = MASS;
vertices[i].vel = initVel;
}
backStep = 0;
damping = DAMPING;
return self;
}
- setSelectedIndex:sender
{
int val;
val = [sender selectedRow];
if (selectedIndex == val) return self;
selectedIndex = val;
[self frameChanged: &bounds];
[self display];
return self;
}
- kickIt:sender
{
int i;
float x,y,z;
x = randBetween(-INIT_VELOCITY, INIT_VELOCITY);
y = randBetween(-INIT_VELOCITY, INIT_VELOCITY);
z = randBetween(-INIT_VELOCITY, INIT_VELOCITY);
for (i = 0; i < numVertices; i++)
{
vertices[i].vel.x += x;
vertices[i].vel.y += y;
vertices[i].vel.z += z;
}
return self;
}
- inspector:sender
{
char buf[MAXPATHLEN];
if (!inspectorPanel)
{
sprintf(buf,"%s/Polyhedra.nib",[sender moduleDirectory:"Polyhedra"]);
[NXApp loadNibFile:buf owner:self withNames:NO];
}
return inspectorPanel;
}
- (BOOL) useBufferedWindow
{ return YES;
}
@end